/*
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

template <typename CflowHandler>
class CflowIterInstructionHandler {
public:
    CflowIterInstructionHandler(const uint8_t* pc, const uint8_t* from, const uint8_t* to, CflowHandler handler)
        : inst_(pc, from, to), handler_(std::move(handler)) {
    }

    uint8_t GetPrimaryOpcode()
    {
        return inst_.GetPrimaryOpcode();
    }

    uint8_t GetSecondaryOpcode()
    {
        return inst_.GetSecondaryOpcode();
    }

    bool IsPrimaryOpcodeValid() const
    {
        return inst_.IsPrimaryOpcodeValid();
    }

% array_first_funcs_names = []
% dispatch_table_hash = Hash.new()
% Panda::instructions.uniq{|i| i.mnemonic}.each do |i|
%   combination_flags = ""
%   mnemonic = i.mnemonic.split('.').map { |p| p == '64' ? 'Wide' : p.capitalize }.join
%   value_dispatch = %(template <BytecodeInstructionSafe::Format FORMAT> \n) +
%                    %(std::optional<VerificationStatus> Handle) + mnemonic + %(())
%   combination_flags += "Valid_"
%   if !i.exceptions.include?('x_none')
%       combination_flags += "Except_"
%   else
%       combination_flags += "ExceptNo_"
%   end
%   if i.properties.include?('jump')
%       if i.properties.include?('conditional')
%           combination_flags += "CondJump_"
%       else
%           combination_flags += "NoncondJump_"
%       end
%   elsif i.stripped_mnemonic == 'throw'
%       combination_flags += "InstTypeThrow_"
%   elsif i.properties.include?('return')
%       combination_flags += "InstTypeReturn_"
%   else
%       combination_flags += "InstTypeNormal_"
%   end
%   if i.properties.include?('jump')
%       combination_flags += "HasJump_"
%   else
%       combination_flags += "HasNoJump_"
%   end
%   combination_flags += "GetNext_"
%   func_name = ("Handle_" + (combination_flags.sub("Valid_", "")).sub("GetNext_", "")).chomp("_")
%   flag = dispatch_table_hash.include?(combination_flags)
%   if flag == false
%       dispatch_table_hash[combination_flags] = []
%       array_first_funcs_names.push(func_name)
%   end
%   dispatch_table_hash[combination_flags].push(value_dispatch)
% end
%
% body_gen_parts = Hash.new()
% body_gen_parts = {
%    "Valid_" => %(
%        if (!inst_.IsValid()) {
%            LOG(DEBUG, VERIFIER) << "Next instruction offset is out of bounds of method body.";
%            return VerificationStatus::ERROR;
%        }
%        const uint8_t* pc = inst_.GetAddress();
%        const size_t sz = inst_.Size(FORMAT);
%    ),
%    "Except_" => %(
%        constexpr bool EXC_SRC = true;
%    ),
%    "ExceptNo_" => %(
%        constexpr bool  EXC_SRC = false;
%    ),
%    "CondJump_" => %(
%        const InstructionType instType = InstructionType::COND_JUMP;
%        LOG_INST();
%    ),
%    "NoncondJump_" => %(
%        const InstructionType instType = InstructionType::JUMP;
%        LOG_INST();
%    ),
%    "InstTypeThrow_" => %(
%        const InstructionType instType = InstructionType::THROW;
%        LOG_INST();
%    ),
%    "InstTypeReturn_" => %(
%        const InstructionType instType = InstructionType::RETURN;
%        LOG_INST();
%    ),
%    "InstTypeNormal_" => %(
%        const InstructionType instType = InstructionType::NORMAL;
%        LOG_INST() << (EXC_SRC ? " (exception source)" : "" );
%    ),
%    "HasJump_" => %(
%        auto imm = inst_.GetImm<FORMAT>();
%        if (!inst_.IsValid()) {
%            LOG(ERROR, VERIFIER) << "Jump instruction imm field is out of bounds of method body. "
%                                 << "Jump instruction offset: " << std::hex << static_cast<uint32_t>(reinterpret_cast<uintptr_t>(pc) - reinterpret_cast<uintptr_t>(inst_.GetFrom()));
%            return VerificationStatus::ERROR;
%        }
%        auto targetInst = inst_.JumpTo(imm);
%        const uint8_t* jumpPc = targetInst.GetAddress();
%        if (!targetInst.IsValid()) {
%            auto offset = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(pc) - reinterpret_cast<uintptr_t>(inst_.GetFrom()));
%            LOG(ERROR, VERIFIER) << "Jump offset is out of bounds of method body. "
%                                 << "Jump instruction offset: 0x" << std::hex << offset
%                                 << ". Jump target offset: 0x" << std::hex << (imm + offset);
%            return VerificationStatus::ERROR;
%        }
%    ),
%    "HasNoJump_" => %(
%        const uint8_t* jumpPc = nullptr;
%    ),
%    "GetNext_" => %(
%        inst_ = inst_.GetNext<FORMAT>();
%        return handler_(instType, pc, sz, EXC_SRC, jumpPc);
%    )
% }
%
% full_code_hash = Hash.new("")
% dispatch_table_hash.each { |key_comb, value_comb|
%    body_gen_parts.each { |key_parts, value_parts|
%        string_to_compare_with = key_comb.to_s
%        flag_compare = string_to_compare_with.include?(key_parts.to_s)
%        if flag_compare == true
%            full_code_hash[key_comb] += value_parts
%        end
%    }
% }
%
%
% array_first_funcs_names.zip(full_code_hash.values).each { |func_name, code|
    template <BytecodeInstructionSafe::Format FORMAT>
    // NOLINTNEXTLINE(readability-identifier-naming)
    std::optional<VerificationStatus> <%= func_name %>() {
    <%= code%>
    }
%}
private:
    BytecodeInstructionSafe inst_;
    CflowHandler handler_;
};

template <typename CflowHandler>
// NOLINTNEXTLINE(readability-function-size)
VerificationStatus IterateOverInstructions(const uint8_t* pc, const uint8_t* from, const uint8_t* to, CflowHandler cflowHandler) {
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvoid-ptr-dereference"
#pragma clang diagnostic ignored "-Wgnu-label-as-value"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif

    // NOLINTNEXTLINE(readability-magic-numbers)
    std::array<const void*, <%= Panda::dispatch_table.handler_names.size %>> dispatchTable{
% Panda::dispatch_table.handler_names.each do |name|
        &&HANDLE_<%= name %>,
% end
    };

    CflowIterInstructionHandler handler(pc, from, to, std::move(cflowHandler));
    std::optional<VerificationStatus> status;
    uint8_t secondaryOpcode;

    if (!handler.IsPrimaryOpcodeValid()) {
        LOG(ERROR, VERIFIER) << "Incorrect opcode";
        return VerificationStatus::ERROR;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
    goto* dispatchTable[handler.GetPrimaryOpcode()];

% hash_handle_variants = Hash.new()
% array_to_paste_zip = array_first_funcs_names.zip(dispatch_table_hash.values)
% Panda::instructions.each do |i|
%   mnemonic = i.mnemonic.split('.').map { |p| p == '64' ? 'Wide' : p.capitalize }.join
%   array_to_paste_zip.each { | func, name_array|
%   flag = false
%   name_to_past = ""
%   name_array.each { |item |
%       flag = item.include?("Handle" + mnemonic.to_s)
%       if flag == true
%           name_to_past = func
%           break
%       end
%   }
%   if flag == true
%       status = "handler.template " + name_to_past + %(<BytecodeInstructionSafe::Format::) + i.format.pretty.upcase + %(>();)
%       flag_array_handle_variants = hash_handle_variants.include?(status)
%       if flag_array_handle_variants == false
%           hash_handle_variants[status] = []
%       end
%       hash_handle_variants[status].push("HANDLE_" + i.handler_name)
%       break
%   end
% }
% end
% hash_handle_variants.each { |key_status, labels_arr|
%   labels_arr.each { |item|
<%= item%>:;
%   }
    status = <%= key_status%>
    // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
    goto COMMON_BODY;
% }

COMMON_BODY:
{
    if (status) { return *status; }
    if (!handler.IsPrimaryOpcodeValid()) {
        LOG(DEBUG, VERIFIER) << "Opcode value is out of range. "
                             << "Current value is: " << static_cast<int>(handler.GetPrimaryOpcode())
                             << ". Allowed value is in the interval: [0, <%= Panda::dispatch_table.invalid_non_prefixed_interval.min - 1 %>] U "
                             << "[<%= Panda::dispatch_table.invalid_non_prefixed_interval.max + 1 %>, <%= Panda::dispatch_table.invalid_prefixes_interval.min + 1 %>] U "
                             << "[<%= Panda::dispatch_table.invalid_prefixes_interval.max + 1 %>, 255]";
        return VerificationStatus::ERROR;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
    goto* dispatchTable[handler.GetPrimaryOpcode()];
}
HANDLE_INVALID:
    LOG(DEBUG, VERIFIER) << "Opcode value is out of range. "
                         << "Current value is: " << static_cast<int>(handler.GetPrimaryOpcode())
                         << ". Allowed value is in the interval: [0, <%= Panda::dispatch_table.invalid_non_prefixed_interval.min - 1 %>] U "
                         << "[<%= Panda::dispatch_table.invalid_non_prefixed_interval.max + 1 %>, <%= Panda::dispatch_table.invalid_prefixes_interval.min + 1 %>] U "
                         << "[<%= Panda::dispatch_table.invalid_prefixes_interval.max + 1 %>, 255]";
    return VerificationStatus::ERROR;
% Panda::prefixes.each do |p|
HANDLE_<%= p.handler_name %>:
    secondaryOpcode = handler.GetSecondaryOpcode();
    LOG(DEBUG, VERIFIER) << "CFLOW: Prefix subdispatch: " << "<%= p.name %>, " << static_cast<int32_t>(secondaryOpcode);

    // NOLINTNEXTLINE(readability-magic-numbers)
    if (secondaryOpcode > <%= Panda::dispatch_table.secondary_opcode_bound(p) %> ) {
        LOG(ERROR, VERIFIER) << "Incorrect opcode";
        return VerificationStatus::ERROR;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto,readability-magic-numbers)
    goto *dispatchTable[<%= Panda::dispatch_table.secondary_opcode_offset(p) %> + secondaryOpcode];
% end

#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
}
